/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package kursova_perzeptron;

import java.util.Random;

/**
 *
 * @author ruslan
 */
public class DeltaBarDelta {

    final int NUM_INPUTS = 4;
    final int NUM_PATTERNS = 15;
    final int NUM_HIDDEN = 4;
    final int NUM_EPOCHS = 1200;
    final double LR_IH = 0.7;
    final double LR_HO = 0.07;
    int patNum;
    double hiddenVal[] = new double[NUM_HIDDEN];
    double weightsIH[][] = new double[NUM_INPUTS][NUM_HIDDEN];
    double weightsHO[] = new double[NUM_HIDDEN];
    double[] trainOutput = new double[NUM_PATTERNS];
    double error = 0.0;
    double outPred = 0.0;
    double momentum = 0.8;
    double[][] trainInputs = {{18.9, 10.0, 8.6, 22},
        {12.6, 15.1, 14.4, 10},
        {14.1, 7.8, 12.3, 12},
        {15.0, 9.5, 12.8, 8},
        {15.6, 12.4, 9.0, 17},
        {17.8, 16.7, 7.5, 5},
        {10.7, 22.2, 11.9, 14},
        {11.5, 17.4, 18.2, 7},
        {10.9, 15.6, 17.9, 8},
        {12.6, 40.1, 8.5, 4},
        {9.5, 27.8, 12.4, 5},
        {8.8, 18.9, 25.0, 3},
        {12.0, 33.5, 8.6, 3},
        {9.0, 14.7, 19.7, 7},
        {8.5, 15.0, 22.4, 4}};
    double[] trainOutputs = {
        Math.random() * 0.5,
        Math.random() * 0.5,
        Math.random() * 0.5,
        Math.random() * 0.5,
        Math.random() * 0.5,
        Math.random() * 0.5,
        Math.random() * 0.5,
        Math.random() * 0.5,
        0.5 + Math.random() * 0.5,
        0.5 + Math.random() * 0.5,
        0.5 + Math.random() * 0.5,
        0.5 + Math.random() * 0.5,
        0.5 + Math.random() * 0.5,
        0.5 + Math.random() * 0.5,
        0.5 + Math.random() * 0.5};

    void algorithm() {
        int patNum = 0;
        initWeights();

        // навчання мережі
        for (int j = 0; j < NUM_EPOCHS; j++) {
            for (int i = 0; i < NUM_PATTERNS; i++) {
                patNum = new Random().nextInt(4);
                calculate(patNum);
                // оновлення ваг
                WeightChangesHO();
                WeightChangesIH(patNum);
            }
        }

        return;
    }

    private void initWeights() {
        for (int j = 0; j < NUM_HIDDEN; j++) {
            weightsHO[j] = (new Random().nextDouble() - 0.5) / 2.0;
            for (int i = 0; i < NUM_INPUTS; i++) {
                weightsIH[i][j] = (new Random().nextDouble() - 0.5) / 5.0;
                System.out.println("Weight = " + weightsIH[i][j]);
            }
        }

        return;
    }

    private void calculate(final int patNum) {

        for (int i = 0; i < NUM_HIDDEN; i++) {
            hiddenVal[i] = 0.0;
            for (int j = 0; j < NUM_INPUTS; j++) {
                hiddenVal[i] += (trainInputs[patNum][j] * weightsIH[j][i]);
            }
            hiddenVal[i] = Math.tanh(hiddenVal[i]);
        }

        outPred = 0.0;

        for (int i = 0; i < NUM_HIDDEN; i++) {
            outPred += hiddenVal[i] * weightsHO[i];
        }

        error = outPred - trainOutput[patNum];
        return;
    }

    private void WeightChangesHO() {
        double barDelta, incConst = 0.01, decConst = 0.01, p;
        double deltaLearningRate;
        barDelta = LR_HO;
        p = barDelta * error;
        if (p > 0) {
            deltaLearningRate = incConst;
        } else if (p < 0) {
            deltaLearningRate = -1.0 * decConst
                    * LR_HO;
        } else {
            deltaLearningRate = 0.0;
        }
        double newLearningRate =
                LR_HO + deltaLearningRate;

        for (int k = 0; k < NUM_HIDDEN; k++) {
            double weightChange = newLearningRate * error * momentum * LR_HO * hiddenVal[k];
            weightsHO[k] += weightChange;

        }
        return;
    }

    private void WeightChangesIH(final int patNum) {
        double barDelta, incConst = 0.01, decConst = 0.01, p;
        double deltaLearningRate;
        barDelta = LR_IH;
        p = barDelta * error;
        if (p > 0) {
            deltaLearningRate = incConst;
        } else if (p < 0) {
            deltaLearningRate = -1.0 * decConst
                    * LR_IH;
        } else {
            deltaLearningRate = 0.0;
        }
        double newLearningRate =
                LR_IH + deltaLearningRate;

        for (int i = 0; i < NUM_HIDDEN; i++) {
            for (int k = 0; k < NUM_INPUTS; k++) {
                double x = 1 - Math.pow(hiddenVal[i], 2);
                x = x * weightsHO[i] * error * newLearningRate * error * (momentum * LR_IH);
                x = x * trainInputs[patNum][k];

                double weightChange = x;
                weightsIH[k][i] -= weightChange;
            }
        }
        return;
    }

    private double calcOverallError() {
        double errorValue = 0.0;

        for (int i = 0; i < NUM_PATTERNS; i++) {
            calculate(i);
            errorValue += Math.pow(error, 2);
        }

        errorValue /= NUM_PATTERNS;

        return Math.sqrt(errorValue);
    }

    double useDBD(double x1, double x2, double x3, double x4) {
        for (int i = 0; i < NUM_HIDDEN; i++) {
            hiddenVal[i] += (trainInputs[patNum][0] * weightsIH[0][i]);

            hiddenVal[i] = Math.tanh(hiddenVal[i]);
        }

        outPred = 0.0;

        for (int i = 0; i < NUM_HIDDEN; i++) {
            outPred += hiddenVal[i] * weightsHO[i];
        }
        return outPred;
    }

    public static void main(String[] args) {
        long timeout = System.currentTimeMillis();
        DeltaBarDelta p = new DeltaBarDelta();
        p.algorithm();
        System.out.println(System.currentTimeMillis() - timeout);
        return;
    }
}
